package cn.demoncat.util.es.dao;

import cn.demoncat.util.es.entity.ClusterInfo;
import cn.demoncat.util.es.entity.IndexInfo;
import cn.demoncat.util.es.entity.NodeInfo;
import cn.demoncat.util.es.util.EsUtil;
import cn.demoncat.util.es.util.QueryUtil;
import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.MapUtil;
import cn.demoncat.util.lang.entity.Pager;
import cn.demoncat.util.web.constant.HttpConstant;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest.Level;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.client.*;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.AnalyzeResponse;
import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.GetMappingsResponse;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * ElasticSearch客户端管理
 * 
 * @author 延晓磊
 *
 * @since 2020年9月1日
 * 
 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html">JAVA API</a>
 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html">REST API</a>
 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html">REST Client API</a>
 */
@Component
public class EsAdminDao{

	/**
	 * Spring Data ElasticSearch
	 */
	@Resource
	private ElasticsearchRestTemplate elasticsearchTemplate;
	/**
	 * ES-REST客户端
	 */
	@Resource
	private RestHighLevelClient elasticsearchRestHighLevelClient;
	
	private static final Logger LOG = LoggerFactory.getLogger(EsAdminDao.class);
	
	/**
	 * 原生REST请求
	 * 
	 * @param method		请求方式，默认GET
	 * @param endpoint		请求端点：URL-PATH
	 * @param parameters	请求参数
	 * @param entity		请求体（JSON）
	 * 
	 * @return 
	 * @author 延晓磊
	 *
	 * @since 2020年9月6日
	 */
	public String rest(String method, String endpoint, Map<String, String> parameters, String entity) {
		// 创建请求
		if (StringUtils.isBlank(method)) {
			method = HttpConstant.Method.GET;
		}
		Request request = new Request(method, endpoint);
		if (MapUtil.isNotEmpty(parameters)) {
			request.addParameters(parameters);
		}
		if (StringUtils.isNotBlank(entity)) {
			request.setJsonEntity(entity);
		}
		// 发起请求
		Response response;
		try {
			response = elasticsearchRestHighLevelClient.getLowLevelClient().performRequest(request);
		} catch (IOException e) {
			throw new BizRuntimeException("请求ES失败", e);
		}
		// 解析响应
		if (response.getStatusLine().getStatusCode() != 200) {
			LOG.error("请求ES失败\r\n" + response);
		}
		String body;
		try {
			body = IoUtil.readToStr(response.getEntity().getContent());
		} catch (UnsupportedOperationException | IOException e) {
			throw new BizRuntimeException("读取ES响应失败", e);
		}
		return body;
	}
	
	/**
	 * 原生REST-GET请求
	 * 
	 * @param endpoint	请求端点：URL-PATH
	 * 
	 * @return 
	 * @author 延晓磊
	 *
	 * @since 2020年9月6日
	 */
	public String rest(String endpoint) {
		return rest(null, endpoint, null, null);
	}
	
	/**
	 * 查询集群状态
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-5
	 * 
	 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html">REST API</a>
	 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-cluster-health.html">JAVA API</a>
	 */
	public ClusterInfo health() {
		// health查询，indices级别
		ClusterHealthRequest req = new ClusterHealthRequest();
		req.level(Level.INDICES);
		ClusterHealthResponse response;
		try {
			response = elasticsearchRestHighLevelClient.cluster().health(req, RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("查询ES集群信息失败", e);
		}
		return ClusterInfo.parse(response);
	}
	
	/**
	 * 查询集群节点
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年12月27日
	 * 
	 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-info.html">REST API</a>
	 */
	public List<NodeInfo> nodes() {
		// 6-版本
		// NodeInfo.parse(client.admin().cluster().nodesInfo(new NodesInfoRequest()).get());
		// 6+版本：官方RestHighLevelClient未封装，使用原生REST请求
		String body = rest("_nodes/http,jvm,os,plugins,settings");
		return NodeInfo.parse(body);
	}
	
	/**
	 * 查询集群索引
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年12月27日
	 * 
	 * @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-stats.html">REST API</a>
	 */
	public List<IndexInfo> indices() {
		// 6-版本
		// IndexInfo.parse(client.admin().indices().prepareStats().all().get());
		// 6+版本：官方RestHighLevelClient未封装，使用原生REST请求
		String body = rest("_stats");
		return IndexInfo.parse(body);
	}
	
	/**
	 * 查询集群完整信息
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年12月27日
	 */
	public ClusterInfo info() {
		// 集群状态
		ClusterInfo cluster = health();
		// 节点状态
		cluster.setNodes(nodes());
		// 索引信息
		cluster.setIndexes(IndexInfo.merge(cluster.getIndexes(),indices()));
		return cluster;
	}
	
	/**
	 * 全部索引
	 * 
	 * @return 索引列表
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月5日
	 */
	public Set<String> indexes(){
		GetAliasesResponse response;
		try {
			response = elasticsearchRestHighLevelClient.indices().getAlias(new GetAliasesRequest(), RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("查询索引失败", e);
		}
		return response.getAliases().keySet();
	}
	
	/**
	 * 全部索引映射
	 * 
	 * @return 索引映射列表
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月5日
	 */
	public Map<String, Map<String, Object>> indexMappings(){
		GetMappingsResponse response;
		try {
			response = elasticsearchRestHighLevelClient.indices().getMapping(new GetMappingsRequest(), RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("查询索引失败", e);
		}
		Map<String,Map<String,Object>> map = new HashMap<>();
		for (Entry<String, MappingMetaData> entry : response.mappings().entrySet()) {
			map.put(entry.getKey(), entry.getValue().getSourceAsMap());
		}
		return map;
	}
	
	/**
	 * 全部索引设置
	 * 
	 * @return 索引设置列表
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月5日
	 */
	public Map<String, Map<String, Object>> indexSettings(){
		GetSettingsResponse response;
		try {
			response = elasticsearchRestHighLevelClient.indices().getSettings(new GetSettingsRequest(), RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("查询索引失败", e);
		}
		Map<String,Map<String,Object>> map = new HashMap<>();
		response.getIndexToSettings().forEach(e ->{
			Map<String,Object> value = new HashMap<>();
			for (String key : e.value.keySet()) {
				value.put(key, e.value.get(key));
			}
			map.put(e.key, value);
		});
		return map;
	}
	
	/**
	 * 分词
	 * 
	 * @param index	索引
	 * @param field	字段
	 * @param text	关键词：使用索引字段对应的分词器
	 * @return 分词列表
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月5日
	 */
	public List<String> analyze(String index, String field, String text) {
		AnalyzeResponse response;
		try {
			response = elasticsearchRestHighLevelClient.indices().analyze(AnalyzeRequest.withField(index, field, text), RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("测试分词失败", e);
		}
		return response.getTokens().stream().map(AnalyzeResponse.AnalyzeToken::getTerm).collect(Collectors.toList());
	}
	
	/**
	 * 分词
	 * 
	 * @param analyzer	分词器
	 * @param text		关键词
	 * @return 分词列表
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月5日
	 */
	public List<String> analyze(String analyzer, String text) {
		AnalyzeResponse response;
		try {
			response = elasticsearchRestHighLevelClient.indices().analyze(AnalyzeRequest.withGlobalAnalyzer(analyzer, text), RequestOptions.DEFAULT);
		} catch (IOException e) {
			throw new BizRuntimeException("测试分词失败", e);
		}
		return response.getTokens().stream().map(AnalyzeResponse.AnalyzeToken::getTerm).collect(Collectors.toList());
	}
	
	/**
	 * 索引操作器
	 * 
	 * @param indexName
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月1日
	 */
	public IndexOperations indexOps(String indexName) {
		return elasticsearchTemplate.indexOps(IndexCoordinates.of(indexName));
	}

	/**
	 * 判断索引是否存在
	 * 
	 * @param indexName
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月1日
	 */
	public boolean existIndex(String indexName) {
		return indexOps(indexName).exists();
	}
	
	/**
	 * 删除索引
	 * 
	 * @param indexName
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月1日
	 */
	public boolean deleteIndex(String indexName) {
		return indexOps(indexName).delete();
	}
	
	/**
	 * 刷新索引
	 * 
	 * @param indexName
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月1日
	 */
	public void refreshIndex(String indexName) {
		indexOps(indexName).refresh();
	}
	
	/**
	 * 创建索引
	 * 
	 * @param indexName
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年9月1日
	 */
	public boolean createIndex(String indexName) {
		return indexOps(indexName).create();
	}
	
	/**
	 * 创建索引
	 * 
	 * @param indexName
	 * @param settings	设置信息
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年9月1日
	 */
	public boolean createIndex(String indexName, Document settings) {
		return indexOps(indexName).create(settings);
	}
	
	/**
	 * 设置索引映射
	 * 
	 * @param indexName
	 * @param mapping	映射信息
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年9月1日
	 */
	public boolean setIndexMapping(String indexName, Document mapping) {
		return indexOps(indexName).putMapping(mapping);
	}
	
	/**
	 * 查看索引设置信息
	 * 
	 * @param indexName
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年9月1日
	 */
	public Map<String, Object> getIndexSettings(String indexName) {
		return indexOps(indexName).getSettings();
	}
	
	/**
	 * 查看索引映射信息
	 * 
	 * @param indexName
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年9月1日
	 */
	public Map<String, Object> getIndexMapping(String indexName) {
		return indexOps(indexName).getMapping();
	}
	
	/**
	 * 默认查询10000以上数据时报错，需要设置max_result_window
	 * 
	 * @param index
	 * @param num	允许查询的条数，默认10亿
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-8
	 */
	public Integer setMaxResultWindow(String index, Integer num) {
		if (num == null || num <=0 ) {
			num = 1000000000;
		}
		rest("PUT", index + "/_settings", null, "{ \"index\" : { \"max_result_window\" : "+num+"}}");
		return num;
	}
	
	/**
	 * 删除文档
	 * 
	 * @param index
	 * @param id
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-3
	 */
	public void deleteDocument(String index, String id) {
		elasticsearchTemplate.delete(id, IndexCoordinates.of(index));
	}
	
	/**
	 * 分页查询（搜索10000条）
	 * 
	 * @param index	索引
	 * @param query	查询条件：空表示全部
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-4
	 */
	public Pager<Object> findPage(String index, Query query, Integer pageNum, Integer pageSize, Sort sort){
		// 查询条件
		if (query == null) {
			query = QueryUtil.all();
		}
		// 分页参数
		Pager<Object> pager = new Pager<>(pageNum, pageSize);
		// 搜索
		SearchHits<Object> result = elasticsearchTemplate.search(QueryUtil.addPage(query, QueryUtil.getPage(pager, sort)), Object.class, IndexCoordinates.of(index));
		// 转换结果
		pager.bindTotalCount(result.getTotalHits());
		pager.setList(EsUtil.getList(result));
		return pager;
	}
	
	/**
	 * 分页查询（搜索全部命中）
	 * 
	 * @param index	索引
	 * @param query	查询条件：空表示全部
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-4
	 */
	public Pager<Object> findAllPage(String index, Query query, Integer pageNum, Integer pageSize, Sort sort){
		// 查询条件
		if (query == null) {
			query = QueryUtil.all();
		}
		// 分页参数
		Pager<Object> pager = new Pager<>(pageNum, pageSize);
		// 搜索
		SearchHits<Object> result = elasticsearchTemplate.search(QueryUtil.addAllPage(query, QueryUtil.getPage(pager, sort)), Object.class, IndexCoordinates.of(index));
		// 转换结果
		pager.bindTotalCount(result.getTotalHits());
		pager.setList(EsUtil.getList(result));
		return pager;
	}
}
